package ga.selectionMethods;

import ga.GARun;
import ga.individuals.Individual;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;

public class Roulette implements SelectionReplacementMethod {
	private Integer numberOfElements;
	
	public Roulette(Integer numberOfElements) throws IllegalArgumentException {
		if (numberOfElements < 0 || numberOfElements > GARun.popSize) {
			throw new IllegalArgumentException("Invalid number of elements!");
		}
		this.numberOfElements = numberOfElements;
	}
	
	private TreeMap<Double, Individual> getAcumFitness (Collection<Individual> individuals) {
		Double totalFitness = 0.0, acum = 0.0, relativeFitness = 0.0;
		TreeMap<Double, Individual> acumFitness = new TreeMap<Double, Individual>();
		
		/* Obtengo la suma de aptitudes */
		for (Individual individual: individuals) {
			totalFitness += individual.getFitnessValue();
		}
		
		/* Calculo de las aptitudes relativas y finalmente las acumuladas */
		for (Individual individual : individuals) {
			relativeFitness = getRelativeFitness(individual, totalFitness);
			acumFitness.put(relativeFitness + acum, individual);
			acum += relativeFitness;
		}
		
		return acumFitness;
	}
	
	private Double getRelativeFitness(Individual individual, Double totalFitness) {
		return individual.getFitnessValue() / totalFitness;
	}

	@Override
	public Collection<Individual> method(Collection<Individual> individuals) {
		TreeMap<Double, Individual> acumFitness = getAcumFitness(individuals);
		List<Individual> selection = new ArrayList<Individual>();
		Double rnd;
		
		/* Por cada random generado, obtiene el Individuo de minimo valor de aptitud
		 * superior al valor random y lo agrega a la lista de individuos seleccionados!
		 */
		for (int i = 0 ; i < this.numberOfElements && i < individuals.size(); i++) {
			rnd = Math.random();
			/* Obtengo el individuo cuyo valor de aptitud acumulado es mayor a rnd pero menos al valor del individuo siguiente */
			selection.add(acumFitness.higherEntry(rnd).getValue());
		}
		
		return selection;
	}
}
